using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Annotations;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using Microsoft.Expression.Controls;
using Microsoft.Expression.Interactivity.Core;
using UIExample.View;
using WK35705XML;
using WK35705XML.Annotations;
using UMP = WK35705XML.UMP;

namespace UIExample.ViewModel
{
	/// <summary>
	/// This class contains properties that the main View can data bind to.
	/// <para>
	/// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
	/// </para>
	/// <para>
	/// You can also use Blend to data bind with the tool'SecondLinkElement support.
	/// </para>
	/// <para>
	/// See http://www.galasoft.ch/mvvm
	/// </para>
	/// </summary>
	public class MainViewModel : ViewModelBase
	{
		private UMPs _umPs;
		private UMPViewModel _selectedUMP;
		private RelayCommand<MouseEventArgs> _outputBindingAttempt;
		private string _outfilter   ;
		private string _infilter;
		private RelayCommand<DragEventArgs> _link;
		private ObservableCollection<UMPLink> _linkCollection = new ObservableCollection<UMPLink>();
		private RelayCommand<MouseEventArgs> _dragging;
		private string _activeSet;
		private RelayCommand<MouseEventArgs> _mouseOn;
		private RelayCommand<MouseEventArgs> _mouseUp;
		private RelayCommand _clear;
		private ObservableCollection<UMPViewModel> _deployedUMPstest = new ObservableCollection<UMPViewModel>();
	    private RelayCommand _save  ;
	    private RelayCommand _load;


	    /// <summary>
		/// Initializes a new instance of the MainViewModel class.
		/// </summary>
		public MainViewModel()
		{
			
				try
				{
					var UMPfile = new FileInfo(@"C:\Users\Jon\SkyDrive\Chalmers\UMP3.xml");
					var s = UMPfile.Open(FileMode.Open, FileAccess.Read);
					UMPs = UMPHelp.Load(s);

					s.Close();

					SelectedUMP = new UMPViewModel() {UMP = UMPs.Items.FirstOrDefault()};
					// DeployedUMPs.Add(UMPs.Items.FirstOrDefault());
				}
				catch (Exception)
				{
					
			   }
			
			DeployedUMPs = new ObservableCollection<UMPViewModel>();
			////else 
			////{
			////    // Code runs "for real"
			////}
		}

		public UMPs UMPs
		{
			get
			{
				return _umPs;
				
			}
			set
			{
				_umPs = value;
				RaisePropertyChanged("UMPs");
				RaisePropertyChanged("UMPItems");
				
			}
		}

		public IEnumerable<WK35705XML.UMP> UMPItems
		{
			get
			{
				IEnumerable<WK35705XML.UMP> resUm = UMPs == null ? null : UMPs.Items;
				if (resUm == null)
					return null;
				if (InFilter != null)
					resUm = UMPs == null ? null : resUm.Where(t => t.Input.Any(d => d.type == InFilter));
				if (OutFilter != null)
					return UMPs == null ? null : resUm.Where(t => t.Output.Any(d => d.type == OutFilter));
				
				return resUm;
			}
		}

		public ObservableCollection<UMPViewModel> DeployedUMPs { get; set; }

		public  RelayCommand LoadUMPXML
		{
			get
			{
				return new RelayCommand(async () =>
				{
					UMPs = await UMPHelp.OpenUMPFile();

				});
			}
		}

		public UMPViewModel SelectedUMP
		{
			get { return _selectedUMP; }
			set
			{
				_selectedUMP = value;
				RaisePropertyChanged("SelectedUMP");
			}
		}
		public RelayCommand<MouseEventArgs> SetFilterStartDragDrop
		{
			get
			{
				if(_outputBindingAttempt == null)
					_outputBindingAttempt = new RelayCommand<MouseEventArgs>(
						(MouseEventArgs p) =>
						{
							p.Handled = true;
							var frameworkElement = p.Source as FrameworkElement;
							if (frameworkElement != null)
							{
								var IO = frameworkElement.DataContext as IOParameterViewModel;
								if (IO != null)
								{
								   if (IO.IO is WK35705XML.UMPOutput)
								   {
									   InFilter = IO.IO.type;
										OutFilter =  "";
										MessengerInstance.Send(new NotificationMessage(IO.IO, InFilter));
									}
									else
									{
										OutFilter = IO.IO.type;
										InFilter =  "";
										MessengerInstance.Send(new NotificationMessage(IO.IO, OutFilter));
									}
								   if (p.MouseDevice.LeftButton == MouseButtonState.Pressed)
								   {
									   DragDrop.DoDragDrop(frameworkElement, frameworkElement, DragDropEffects.Link);
									   MouseUp.Execute(null);

								   }
								}
								
							}
						});
				return _outputBindingAttempt;
			}
		}



		public string InFilter
		{
			get { return _infilter; }
			set
			{
				_infilter = value;
				
				RaisePropertyChanged("InFilter");
				RaisePropertyChanged("UMPItems");
				RaisePropertyChanged("FilterOff");
			}
		}
		public string OutFilter
		{
			get { return _outfilter; }
			set
			{
				_outfilter = value;
				RaisePropertyChanged("OutFilter");
				RaisePropertyChanged("UMPItems");

			}
		}


		public RelayCommand<DragEventArgs> Link
		{
			get
			{
				if(_link != null)
					return _link;
				_link = new RelayCommand<DragEventArgs>((e) =>
				{
					e.Handled = false;
					MouseUp.Execute(null);
					var s = e.Source as FrameworkElement;
					var i = s.DataContext as IOParameterViewModel;
					if(i == null)
						return;
					if (e.Data.GetFormats().Length > 0)
					{
						var d = e.Data.GetData(e.Data.GetFormats()[0]);
						if (d is FrameworkElement )
						{
						    var l = d as FrameworkElement;
						    LinkUMPs(l, i, s);
						}
					}
				});
				return _link;
			}

		}

	    private void LinkUMPs(FrameworkElement firstLinkElement, IOParameterViewModel i, FrameworkElement secondLinkElement)
	    {
	        if (firstLinkElement.DataContext is IOParameterViewModel && i.IO is UMPInput)
	        {
	            var o = firstLinkElement.DataContext as IOParameterViewModel;
	            var link = new UMPLink()
	            {
	                In = i,
	                Out = o,
	                InGrafic = secondLinkElement,
	                OutGrafic = firstLinkElement
	            };
	            link.PropertyChanged += link_PropertyChanged;
	            LinkCollection.Add(link);
	        }
	        else if (firstLinkElement.DataContext is IOParameterViewModel && i.IO is UMPOutput)
	        {
	            var o = firstLinkElement.DataContext as IOParameterViewModel;
	            var link = new UMPLink()
	            {
	                In = o,
	                Out = i,
	                InGrafic = firstLinkElement,
	                OutGrafic = secondLinkElement
	            };
	            link.PropertyChanged += link_PropertyChanged;
	            LinkCollection.Add(link);
	        }
	    }

	    void link_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			var s = sender as UMPLink;
			if(s != null && s.Remove==true )
			LinkCollection.Remove(s);
		}

		public ObservableCollection<UMPLink> LinkCollection
		{
			get { return _linkCollection; }

			set
			{
				_linkCollection = value;
				RaisePropertyChanged("LinkCollection");
			}
		}

		public RelayCommand<MouseEventArgs> Dragging
		{
			get
			{
				if(_dragging == null)
					_dragging = new RelayCommand<MouseEventArgs>((e) =>
					{
						if (e.LeftButton == MouseButtonState.Pressed)
						{
							var l = _linkCollection.ToArray();
							LinkCollection = new ObservableCollection<UMPLink>();
							foreach ( var i in l)
							{
								_linkCollection.Add(i);
							}
						}
					});
				return _dragging;
			}
		}

		public string ActiveSet
		{
			get { return _activeSet; }
			set
			{
				if (value == _activeSet) return;
				_activeSet = value;
				RaisePropertyChanged("ActiveSet");
				foreach (var umpViewModel in DeployedUMPs)
				{
					umpViewModel.Refresh();
				}
			   
			}
		}

		public IEnumerable<string> Sets
		{
			get
			{

				var ttt = DeployedUMPs.SelectMany(t => t.UMP.Transformation.Equation.Select(s => s.set).Distinct()).Distinct();
				if (ActiveSet == null || !ttt.Contains(ActiveSet))
				{
					ActiveSet = ttt.FirstOrDefault();
				}
				return ttt;
			}

		}

		public RelayCommand<MouseEventArgs> MouseOn
		{
			get
			{
				if (_mouseOn == null)
					_mouseOn = new RelayCommand<MouseEventArgs>((e) =>
					{
						var frameworkElement = e.Source as FrameworkElement;
						if (e.MouseDevice.DirectlyOver == frameworkElement && frameworkElement != null)
						{
							var IO = frameworkElement.DataContext as WK35705XML.IUMPIO;
							if (IO != null)
								MouseOnIO = IO;
						}

					});
				return _mouseOn; 
			}
		}

		public static IUMPIO MouseOnIO { get; set; }

		public RelayCommand<MouseEventArgs> MouseUp
		{
			get
			{
				if (_mouseUp == null)
					_mouseUp = new RelayCommand<MouseEventArgs>((e) =>
					{
						
						//e.Handled = true;
						InFilter = null;
						OutFilter = null;
						MessengerInstance.Send(new NotificationMessage(new WK35705XML.UMPInput(), null));

					});
				return _mouseUp;
			}
		}

		public bool FilterOff => InFilter == null;

	    public RelayCommand Clear
		{
			get
			{
				if (_clear == null)
					_clear = new RelayCommand(() =>
					{

						DeployedUMPs.Clear();
						

					});
				return _clear;
			}
		}

	    public RelayCommand SaveSystem
	    {
            get
            {
                if (_save == null)
                    _save = new RelayCommand(async () =>
                    {
                        var list = new List<SystemLinkingVariable>();
                        foreach (var umpLink in LinkCollection)
                        {
                            list.Add(new SystemLinkingVariable()
                            {
                                allocationVariable = 1,
                                allocationVariableSpecified = true,
                                sourceUMP = umpLink.Out.ParentViewModel.UMP.name,
                                sourceOutput = umpLink.Out.IO.name,
                                targertUMP = umpLink.In.ParentViewModel.UMP.name,
                                targetInput = umpLink.In.IO.name,
                            });
                        }
                        var system = new SystemAnalyse()
                        {
                            LinkingVariable = list.ToArray(),
                            UMP = UMPItems.ToArray()
                        };
                        await UMPHelp.SaveUMPSystem(system);
                    });
                return _save; ;
            }
	    }

        public  RelayCommand LoadSystem
	    {
            get
            {
                if (_load == null)
                    _load = new RelayCommand(async () =>
                    {
                        var system = await UMPHelp.OpenUMPSystemFile();
                        if(system == null )
                            return;
                        DeployedUMPs.Clear();
                        LinkCollection.Clear();
                        
                        
                        UMPs = new UMPs()
                        {
                            Items = new ObservableCollection<UMP>(system.UMP)
                        };
                        int row = 1;
                        int col = 1;
                        foreach (var umpViewModel in DeployedUMPs)
                        {
                            umpViewModel.Margin = new Thickness(){Top = (row)*100, Right = col * 100};
                            if (row++ == 3)
                            {
                                row = 0;
                                col++;
                            }
                        }
                        foreach (var umpLink in system.LinkingVariable)
                        {
                            LinkCollection.Add(new UMPLink()
                            {
                                
                            });
                        }
                        
                        await UMPHelp.SaveUMPSystem(system);
                    });
                return _load; ;
            }
	    }

	    public void Drop(WK35705XML.UMP ump, Grid grid, Point getPosition)
		{
			var u = ump.Copy();
			
			var context = new UMPViewModel()
			{
				UMP = u,
				Margin = new Thickness(getPosition.X - 94, getPosition.Y - 94, 0, 0)
			};
			DeployedUMPs.Add(context);
			//grid.Children.Add(new UMPView(){DataContext = context,  Margin = new Thickness(getPosition.X - 94, getPosition.Y - 94, 0, 0)});
			RaisePropertyChanged("Sets");
		}

	   


	}

	public class UMPLink : INotifyPropertyChanged
	{
		private RotateTransform _rotate;
		private double _length;
		private FrameworkElement _inGrafic;
		private Thickness _margin;
		private FrameworkElement _outGrafic;
		private bool _remove;
		private static List<UMPLink> parameters = new List<UMPLink>();

		public UMPLink()
		{
			parameters.Add(this);
		}

		~UMPLink()
		{
			parameters.Remove(this);
		}

		public static void ResetVisited()
		{
			foreach (var links in parameters)
			{
				links.Visited = false;
			}
		}

		public static decimal MaxValue()
		{
			return parameters.Max(t => t.Value);
		}

		public decimal Thickness
		{
			get
			{
				if (Value == 0)
					return 3;
				
				return 20*Value/MaxValue();
			}
		}
		


		public IOParameterViewModel Out
		{
			get { return _out; }
			set
			{
				if (Equals(value, _out)) return;
				_out = value;
				_out.ParentViewModel.AddLink(this);
				OnPropertyChanged();
			}
		}

		public FrameworkElement InGrafic
		{
			get { return _inGrafic; }
			set
			{
				if (Equals(value, _inGrafic)) return;
				var g = FindParentGrid(value);
				foreach (var c in g.Children)
				{
					if (c is LineArrow)
					{
						value = c as LineArrow;
						break;
					}
				}
				_inGrafic = value;
				OnPropertyChanged();
				OnPropertyChanged("Margin");
				
				_inGrafic.LayoutUpdated += _inGrafic_LayoutUpdated;
			}
		}

		private DateTime latestupdate = DateTime.Now;
		private IOParameterViewModel _in;
		private IOParameterViewModel _out;
		private decimal _value;

		void _inGrafic_LayoutUpdated(object sender, EventArgs e)
		{
			if(DateTime.Now < latestupdate+TimeSpan.FromMilliseconds(1))
				return;
			Update();
			latestupdate = DateTime.Now;
		}


		public FrameworkElement OutGrafic
		{
			get { return _outGrafic; }
			set
			{
				if (Equals(value, _outGrafic)) return;
				var g = FindParentGrid(value);
				foreach (var c in g.Children)
				{
					if (c is LineArrow)
					{
						value = c as LineArrow;
						break;
					}
				}
			   
				_outGrafic = value;
				OnPropertyChanged();
				_outGrafic.LayoutUpdated += _inGrafic_LayoutUpdated;
			}
		}

		public IOParameterViewModel In
		{
			get { return _in; }
			set
			{
				if (Equals(value, _in)) return;
				_in = value;
				_in.ParentViewModel.AddLink(this);
				OnPropertyChanged();
			}
		}

		public Thickness Margin
		{
			get { return _margin; }
			set
			{
				if (value.Equals(_margin)) return;
				_margin = value;
				OnPropertyChanged();
			}
		}

		public void Update()
		{
			try
			{
				var window = Window.GetWindow(FindParentGrid(InGrafic));

				Point inpoint = InGrafic.TransformToAncestor(window)
					.Transform(new Point(0, 0));
				Point outpoint = OutGrafic.TransformToAncestor(window)
					.Transform(new Point(0, 0));
				outpoint.X += _outGrafic.ActualWidth;
				outpoint.Y -= 5;
				inpoint.Y -= 5;

				var left = outpoint.X;
				var top = outpoint.Y;

				var near = outpoint.X - inpoint.X;
				var far = outpoint.Y - inpoint.Y;

				Length = Math.Sqrt(Math.Pow(near, 2) + Math.Pow(far, 2));
				var angle = Math.Acos(near/Length);
				if (inpoint.Y > outpoint.Y)
					angle = Math.Asin(far/Length);
				if (inpoint.Y > outpoint.Y && near < 0)
					angle = Math.PI - angle;
				angle += Math.PI;
				Rotate = new RotateTransform(angle*360/(2*Math.PI));

				Margin = new Thickness(left, top, 0, 0);
			}
			catch 
			{
				Remove = true;
			}
		}

		public bool Remove
		{
			get { return _remove; }
			set
			{
				if (value.Equals(_remove)) return;
				_remove = value;
				OnPropertyChanged();
			}
		}
		public bool Visited { get; set; }
		public double Length
		{
			get { return _length; }
			set
			{
				if (value.Equals(_length)) return;
				_length = value;
				OnPropertyChanged();
			}
		}


		public RotateTransform Rotate
		{
			get { return _rotate; }
			set
			{
				if (_rotate != null && Math.Abs(value.Angle - _rotate.Angle) < 0.1) return;
				_rotate = value;
				OnPropertyChanged();
			}
		}

		public decimal Value
		{
			get { return _value; }
			set
			{
				Visited = true;
				if (value == _value) return;
				_value = value;
				OnPropertyChanged();
				OnPropertyChanged("Thickness");
			}
		}

		private static Grid FindParentGrid(FrameworkElement f)
		{
			if (f == null)
				return null;
			if (f is Grid)
				return f as Grid;
			if (f.Parent != null && f.Parent is FrameworkElement)
				return FindParentGrid(f.Parent as FrameworkElement);
			return null;
		}

		public event PropertyChangedEventHandler PropertyChanged;

		[NotifyPropertyChangedInvocator]
		protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
		{
			PropertyChangedEventHandler handler = PropertyChanged;
			if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
		}
	}
}